//
//  simplest_eagl_rgb_render.m
//  QH_Simplest_EAGL_Demo
//
//  Created by Anakin chen on 2021/2/17.
//

#import "simplest_eagl_rgb_render.h"

#import "simplest_eagl_rgb_render_view.h"
#import "simplest_eagl_rgb_render_shader.h"
#import "QHEAGLUtil.h"

@interface simplest_eagl_rgb_render ()

@property (nonatomic) int type;

@property (nonatomic, strong) simplest_eagl_rgb_render_view *pview;

@property (nonatomic) GLuint frameBuffer;
@property (nonatomic) GLuint renderBuffer;
@property (nonatomic) GLuint texId;
@property (nonatomic) GLuint filterProgram;

@property (nonatomic) GLint backingWidth;
@property (nonatomic) GLint backingHeight;
@property (nonatomic) NSInteger frameWidth;
@property (nonatomic) NSInteger frameHeight;

@property (nonatomic) GLint filterPositionAttribute;
@property (nonatomic) GLint filterTextureCoordinateAttribute;
@property (nonatomic) GLint filterInputTextureUniform;

@property (nonatomic) BOOL changeWIImage;
@property (nonatomic) GLuint texId2;
@property (nonatomic) GLint filterTextureCoordinateAttribute2;
@property (nonatomic) GLint filterInputTextureUniform2;

@property (nonatomic) BOOL bPipeProgram;
@property (nonatomic) GLuint texId3;

@end

@implementation simplest_eagl_rgb_render

+ (instancetype)createWith:(UIView *)preview type:(int)type {
    simplest_eagl_rgb_render *this = [simplest_eagl_rgb_render new];
    this.preview = preview;
    this.type = type;
    [this p_setup];
    return this;
}

- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    if (dispatch_semaphore_wait(self.frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0) {
        return;
    }
    
    CFRetain(sampleBuffer);
    dispatch_async(self.videoQueue, ^{
        CVImageBufferRef pixBuf = CMSampleBufferGetImageBuffer(sampleBuffer);
        CMTime currentTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
        
        [self p_processPixelBuffer:pixBuf time:currentTime];
        
        dispatch_semaphore_signal(self.frameRenderingSemaphore);
        CFRelease(sampleBuffer);
    });
}

- (void)addWbImage {
    dispatch_async(self.videoQueue, ^{
        if ([EAGLContext currentContext] != self.eaglContext) {
            [EAGLContext setCurrentContext:self.eaglContext];
        }
        if (self.type == 1) {
            [self p_addWbImage3];
        }
        else {
            [self p_addWbImage];
        }
    });
}

#pragma mark - Pirvate

- (void)p_setup {
    [super p_setup];
    
    _pview = [[simplest_eagl_rgb_render_view alloc] initWithFrame:self.preview.bounds];
    [self.preview addSubview:_pview];
    
    dispatch_sync(self.videoQueue, ^{
        [EAGLContext setCurrentContext:self.eaglContext];
        
        if (![self p_createDisplayFramebuffer]) {
            NSLog(@"create Dispaly Framebuffer failed...");
        }
        [self p_createProgram];
        
        glViewport(0, 0, _backingWidth, _backingHeight);
    });
}

- (void)p_processPixelBuffer:(CVPixelBufferRef)pixelBuffer time:(CMTime)timeInfo {
    CVImageBufferRef pixBuf = pixelBuffer;
    
    if ([EAGLContext currentContext] != self.eaglContext) {
        [EAGLContext setCurrentContext:self.eaglContext];
    }
    
    int bufferWidth = (int) CVPixelBufferGetWidth(pixBuf);
    int iHeight = (int) CVPixelBufferGetHeight(pixBuf);
    
    CVPixelBufferLockBaseAddress(pixBuf, 0);
    
    int iBytesPerRow = (int) CVPixelBufferGetBytesPerRow(pixBuf);
    
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    if (_frameWidth != iBytesPerRow / 4) {
        _frameWidth = iBytesPerRow / 4;
        _frameHeight = iHeight;
        glViewport(0, 0, (int)_frameWidth, (int)_frameHeight);
    }
    
    [self p_renderFrame:pixBuf];
    
    glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
    [self.eaglContext presentRenderbuffer:GL_RENDERBUFFER];
    
    CVPixelBufferUnlockBaseAddress(pixBuf, 0);
}

- (BOOL)p_createDisplayFramebuffer {
    BOOL ret = TRUE;
    
    glGenFramebuffers(1, &_frameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    
    glGenRenderbuffers(1, &_renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
    
    [self.eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.pview.layer];
    
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
    
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"failed to make complete framebuffer object %x", status);
        return FALSE;
    }
    
    GLenum glError = glGetError();
    if (GL_NO_ERROR != glError) {
        NSLog(@"failed to setup GL %x", glError);
        return FALSE;
    }
    return ret;
}

- (void)p_createProgram {
    if ([self p_buildProgram]) {
        glGenTextures(1, &_texId);
        glBindTexture(GL_TEXTURE_2D, _texId);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)_frameWidth, (GLsizei)_frameHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        glBindTexture(GL_TEXTURE_2D, 0);
    }
}

- (BOOL)p_buildProgram {
    BOOL result = NO;
    
    _filterProgram = glCreateProgram();
    
    GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vvertexShaderString);
    GLuint fragmentShader;
    if (self.type == 1) {
        fragmentShader = compileShader(GL_FRAGMENT_SHADER, rgbFragmentShaderString);
    }
    else {
        fragmentShader = compileShader(GL_FRAGMENT_SHADER, rgbFragmentShaderString2);
    }
    
    glAttachShader(_filterProgram, vertexShader);
    glAttachShader(_filterProgram, fragmentShader);
    glLinkProgram(_filterProgram);
    
    _filterPositionAttribute = glGetAttribLocation(_filterProgram, "position");
    _filterTextureCoordinateAttribute = glGetAttribLocation(_filterProgram, "texcoord");
    _filterInputTextureUniform = glGetUniformLocation(_filterProgram, "inputImageTexture");
    _filterInputTextureUniform2 = glGetUniformLocation(_filterProgram, "inputImageTexture2");
    
    glUseProgram(_filterProgram);
    glEnableVertexAttribArray(_filterPositionAttribute);
    glEnableVertexAttribArray(_filterTextureCoordinateAttribute);
    
    GLint status;
    glGetProgramiv(_filterProgram, GL_LINK_STATUS, &status);
    if (status == GL_FALSE) {
        NSLog(@"Failed to link program %d", _filterProgram);
        goto exit;
    }
    result = validateProgram(_filterProgram);
    
exit:
    if (vertexShader)
        glDeleteShader(vertexShader);
    if (fragmentShader)
        glDeleteShader(fragmentShader);
    
    if (result) {
        NSLog(@"OK setup GL programm");
    } else {
        glDeleteProgram(_filterProgram);
        _filterProgram = 0;
    }
    return result;
}

- (void)p_renderFrame:(CVPixelBufferRef)pixelBuffer {
    
    CVImageBufferRef pixBuf = pixelBuffer;
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    static const GLfloat imageVertices[] = {
        -1.0f, -1.0f,
        1.0f, -1.0f,
        -1.0f,  1.0f,
        1.0f,  1.0f,
    };

    GLfloat noRotationTextureCoordinates[] = {
        0.0f, 1.0f,
        1.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 0.0f,
    };

    glVertexAttribPointer(_filterPositionAttribute, 2, GL_FLOAT, 0, 0, imageVertices);
    glVertexAttribPointer(_filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, noRotationTextureCoordinates);
    
    if (self.bPipeProgram) {
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, _texId3);
        glUniform1i(_filterInputTextureUniform, 2);
        
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }
    
    if (self.type == 1) {
        CGFloat d = 4;
        glViewport(0, 0, (int)_frameWidth/d, (int)_frameWidth/d);
    }
    
    glBindTexture(GL_TEXTURE_2D, _texId);
    
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)_frameWidth, (GLsizei)_frameHeight,
                 0, GL_RGBA, GL_UNSIGNED_BYTE, CVPixelBufferGetBaseAddress(pixBuf));
    
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, _texId);
    glUniform1i(_filterInputTextureUniform, 1);
    
    if (self.changeWIImage) {
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, _texId2);
        glUniform1i(_filterInputTextureUniform2, 2);
    }

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    
    if (self.type == 1) {
        glViewport(0, 0, (int)_frameWidth, (int)_frameHeight);
    }
}
        
- (void)p_addWbImage {
    self.changeWIImage = YES;
    
    glGenTextures(1, &_texId2);
    glBindTexture(GL_TEXTURE_2D, _texId2);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    unsigned char *pBGRAImageIn;
    [QHUtil input:@"WhiteBoard_rgba" ofType:@"rgb" len:&pBGRAImageIn];
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)1125, (GLsizei)2436,
                 0, GL_RGBA, GL_UNSIGNED_BYTE, pBGRAImageIn);
    glBindTexture(GL_TEXTURE_2D, 0);
}

- (void)p_addWbImage3 {
    self.bPipeProgram = YES;
    
    glGenTextures(1, &_texId3);
    glBindTexture(GL_TEXTURE_2D, _texId3);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    unsigned char *pBGRAImageIn;
    [QHUtil input:@"WhiteBoard_rgba" ofType:@"rgb" len:&pBGRAImageIn];
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)1125, (GLsizei)2436,
                 0, GL_RGBA, GL_UNSIGNED_BYTE, pBGRAImageIn);
    glBindTexture(GL_TEXTURE_2D, 0);
}

@end
